note: a lot of things have changed since i write this doc. don't be too upset... !
Legal note
Z80 engine 68020 based
Copyright (C) 1994-1995
Günter Woigk
Nürnberger Str. 79
91052 Erlangen
Germany
email: kio@vanilla.nbg.sub.org
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.
You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
General
The file "z80.c" is the C/assembler source for an emulator of the Zilog Z80 CPU. It is designed for high speed and ease of use.
At the moment of writing this emulator supports:
• all legal instructions except
INI, IND, INIR, INDR, OUTI, OUTD, OTIR, OTDR
• most illegal instructions
• normal interrupts in all three modes except multi-byte instructions in IM0
• NMI, trace mode and watchdog
• 16 bit i/o addresses
It does not exactly emulate:
• DAA instruction and it's corresponding bits.
• exact timing of Z80 opcodes
• exact incrementation of the F register
• some undocumented opcode side effects
• write protection for ROMs
The emulator is extremely fast. On a 68030/32 it should have the overall performance of a 4 MHz Z80. (neglecting the time an emulator will spend updating a screen)
Procedures
OSErr Init_Z80() allocates and initializes a 64 kByte RAM/ROM core. Actually 128 kByte of RAM are allocated, for the emulator always locates the Z80 RAM at a multiple of 0x10000. It also resets the Z80 CPU and initializes it's register structure and calculates all required conversion tables. And it sets the byte, which the Z80 CPU reads at it's interrupt acknowledge cycle from the data bus to 0xFF, which in most cases is the byte it would read in a real system. On success Init_Z80() returns 0x00; if it could not allocate memory for the Z80 core, it returns a non-zero value.
short Z80() is the routine, which actually interprets and emulates Z80 instructions. On entry, it reads all registers from the structure zreg and on return it writes them back to zreg. The result value indicates the reason why Z80() returned.
The zreg structure
The type for this structure is defined in "Z80.h" and is called regs. Inside regs a union named pair is used to describe registers which may be used as pointers.
pair.ptr is used to address bytes inside the Z80 core. The high word of this address is initialized by InitZ80() and must not be changed. In general: don't use pair.ptr.
pair.rr.corebase or pair.r.corebase is initialized by InitZ80() as mentioned above and must not be modified.
pair.rr.reg may be used to read or write a register pair.
pair.r.hi and pair.r.lo may be used to read or write a single register.
struct regs {
pair BC, DE, HL, IX, IY, IP, RP ;
Short BC2, DE2, HL2 ;
Char AA2, A2, aa, A ;
Char FF2, F2, ff, F ;
Char I, irptcmd ;
Char IFF1, IFF2 ;
Char WUFF ;
Char R ;
Char IM ;
};
zreg.BC to zreg.RP are the most frequently used register pairs.
zreg.BC2 to zreg.HL2 are the exchange register pairs used in the EXX instruction.
zreg.AA2, zreg.FF2, zreg.aa and zreg.ff are initialized to 0x00 by Init_Z80() and must not be changed.
zreg.A and zreg.F are the AF register pair, zreg.A2 and zreg.F2 are their exchange register pair used in EX AF,AF'. Flagbits are stored according to the mc68000 flag register, not as in the Z80 flag register!
zreg.I is the I register; zreg.irptcmd is the byte which the Z80 CPU reads in interrupt acknowledge cycle from data bus. This byte is preset to 0xff by Init_Z80() and may be set to any desired value before signaling an interrupt.
zreg.IFF1 and zreg.IFF2 represent the interrupt enable flag and it's temporaryly saved value during NMI processing.
zreg.WUFF contains 4 bits which may be set from an implementation depending interrupt routine. Their bitmask and meaning is as follows:
0x80 trace flag: forces Z80() to return after every statement
or after processing an interrupt or NMI.
0x40 watchdog interrupt: forces Z80() to return immediately.
0x20 NMI: Z80() processes an NMI cycle and resets the request.
0x10 not used; must be 0
0x0F Normal interrupt: Z80() processes an interrupt cycle reading
zreg.irptcmd from the bus and decrements the request counter by 1.
zreg.R represents the refresh counter of the Z80 CPU. Actually, it is incremented by 7 every time it is read by the LD A,R instruction thus implementing a minimum emulation.
zreg.IM stores the interrupt mode, the Z80 CPU actually runs in.
Return values of Z80()
0 Watchdog exception. May be used by a watchdog timer. The watchdog flag in zreg.WUFF should be cleared befor Z80() is called again. Mac Spectacle uses watchdog interrupts to poll Mac OS events and update the ZX Spectrum screen.
1 Trace exception. The Z80 CPU runs in trace mode and after every instruction or after processing an interrupt or NMI cycle, Z80() returns to the calling application. Note, that your application should not generate interrupts when you trace a routine interactively.
2 A not implemented instruction was executed. zreg.IP points to the first byte of the incriminated opcode. You should abort further execution of this Z80 program, for it has crashed, either due to an error in the emulator or due to an error in the Z80 target application itself.
3 The Z80 instruction HALT was executed. The application should poll Mac OS events until an NMI or normal interrupt occurs. If interrupts are disabled, you must only wait for NMI's. Before calling Z80() again, you must check for trace mode and call your trace mode handler just as it would be called, if a trace exception occured.
4 The Z80 instruction RST 0 was executed. This opcode is trapped as a possibility to call native 68000/PPC code to perform some system routines quicker than they were performed emulating the Z80 code. To decide which system function must be emulated, check the current PC in zreg.IP , which points to the next instruction. Z80() does nothing except returning with code 4, when a RST 0 is executed. If you want to make the RST 0 opcode just do what it does on a real Z80, push the PC on the stack and set the PC to 0x0000. Before calling Z80() again, you must check for trace mode and call your trace mode handler just as it would be called, if a trace exception occured.
5 The emulator trapped an instruction which attempted to write to the ROM. This error only occurs, if the associated compiler option were enabled. The address of this instruction is stored in zreg.IP. Abort further execution of the Z80 target application.
Note, that the zreg.IP normally points to the next instruction, when Z80() returns except for return code 5 (ROM write attempt) and 2 (not implemented opcode), where it points to the faulty instruction.
Input and output instructions
Every input instruction results in Z80() calling Char Do_Input(register Short address) and you must provide this function somewhere in your application.
Every output instruction will call Do_Output(register Short address, register Char value) which must be provided in your application too.
Block i/o commands are not supported in this version of Z80().
Debugger info calls
When compiling "z80.c" you can set a lot of switches in "z80.options" which will result in calls to Do_Info(long PC) . The argument to Do_Info() is the PC of the instruction which was trapped. Don't use zreg do determine Z80 registers and don't write to zreg to modify Z80 registers. Some registers in zreg are valid, others are not. Actually, PC, SP, A, A', F and F' are kept in mc68020 registers and are not valid in zreg, but this may not be true in the next version of Z80(). These calls are for information purpose only and should take as little time as possible. For a complete list of all instructions which have optional switches to call Do_Info() refer to "z80.options".
Write protection for ROM address space
If you set either ROM_protection or ROM_exception in "z80.options", compiling "z80.c" will include code to check destination addresses of write instructions to prevent your ROM from beeing overwritten. The ROM address space is defined by ROM_start and ROM_end in "z80.options". In the actual version of Z80() the following instructions are checked:
ld (addr),n
ld (addr),nn
inc (addr)
dec (addr)
set (addr)
res (addr)
The following instructions are not checked:
block moves
stack operations
It is still possible to overwrite the first byte of the ROM with a ld(addr),nn instruction.
Which Functions must be provided by an application which uses Z80() ?
extern Do_Output ( register Short addr, register Char n ) Handle OUT instructions
Char Do_Input ( register Short addr ) Handle IN instruction
extern Do_Info ( Char *ip ) Called if info_XXXX==on
extern Do_1st_Loc ( Char *ip ) Required if pc_firstuse==on
extern Do_1st_Instr ( Char *ip ) Required if cmd_firstuse==on
Trace Mode Cave At's
• Check for trace mode after performing a HALT or RST 0 instruction and call your trace handler if necessary.
• You should switch off your interrupt generation when tracing Z80 code.
• When EI is performed in trace mode, make shure the next instruction can be executed without beeing interrupted by a normal interrupt, if you have not disabled interrupt generation.
• Normally, the instruction addressed by zreg.IP is executed next. Evaluate Do_Info() calls to see, whether an NMI or (if not switched off) a normal interrupt is processed instead.
Standard Use of the Z80 Engine
Call Init_Z80() to initialize the engine and set the registers to reset condition. After that copy a ROM image into the CORE, set up an interrupt generating routine which triggers the corresponding bits in zreg.WUFF and at last call Z80() in a loop. Every time Z80() returns, examine it's result code and do appropriate action; e.g. wait for next interrupt on a HALT instruction or call your replacement routine for a RST 0 instruction.
Use watchdog interrupts to copy a memory mapped video display to a Macintosh window and to poll Mac OS events. Store Mac OS events in some kind of data and evaluate this data in your Do_Input() and Do_Output() routines.